//
// Created by ken on 8/29/22.
//

#include <android/log.h>
#include "AudioEngine.h"
#include <thread>
#include <mutex>
#include <sys/socket.h>

#define SAMPLE_RATE 44100
//#define LATENCY 0.03
#define MAX_CHAR (unsigned char)(-1)
#define HALF_MAX_CHAR MAX_CHAR/2

#define TAG "AudioEngine"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

aaudio_data_callback_result_t playCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames){
    auto *engine=(AudioEngine*)userData;

    engine->trackMixer->readSound((char*)audioData,numFrames*2);

    short *entry=(short*) audioData;
    for(int i=0;i<numFrames;i++){
        entry[i]= ntohs(entry[i]);
    }

    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

aaudio_data_callback_result_t recordCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames){
    auto *engine=(AudioEngine*)userData;

    short *entry=(short*) audioData;
    for(int i=0;i<numFrames;i++){
        entry[i]= htons(entry[i]);
    }

    engine->vocal_channel->send(audioData,numFrames*2,0);
    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

AudioEngine::AudioEngine() {
    int s=socket(AF_INET, SOCK_DGRAM , 0);
    if(s<0){
        LOGE("create socket error: %s(errno: %d)\n", strerror(errno), errno);
    }
    this->receiver=new Receiver(s);
    this->trackMixer=new Mixer<Trackreceiver>;
    this->musicTrack=new Trackreceiver();
    this->vocalTrack=new Trackreceiver();
    this->trackMixer->addSource(musicTrack);
    this->trackMixer->addSource(vocalTrack);
    this->music_channel=new Channel(music,channel_unreliable,1,s,[=](Packet *p){
        musicTrack->receiveSound(p);
        return true;
    });
    this->vocal_channel=new Channel(vocal,channel_unreliable,1,s,[=](Packet *p){
        vocalTrack->receiveSound(p);
        return true;
    });
    this->receiver->addChannel(music_channel);
    this->receiver->addChannel(vocal_channel);

    this->o=new Oscillator();
    this->o->setWaveOn(true);
    this->o->setSampleRate(SAMPLE_RATE);

    this->receiver->receive_forever();
}

void playErrorCallback(AAudioStream *stream, void *userData, aaudio_result_t error){
    if(error==AAUDIO_ERROR_DISCONNECTED){
        auto restartFunc=std::bind(&AudioEngine::restartPlaying,static_cast<AudioEngine *>(userData));
        new std::thread(restartFunc);
    }
}

void recordErrorCallback(AAudioStream *stream, void *userData, aaudio_result_t error){
    if(error==AAUDIO_ERROR_DISCONNECTED){
        auto restartFunc=std::bind(&AudioEngine::restartRecording,static_cast<AudioEngine *>(userData));
        new std::thread(restartFunc);
    }
}

bool AudioEngine::startPlaying() {
    AAudioStreamBuilder *streamBuilder;
    AAudio_createStreamBuilder(&streamBuilder);
    AAudioStreamBuilder_setFormat(streamBuilder,AAUDIO_FORMAT_PCM_I16);
    AAudioStreamBuilder_setChannelCount(streamBuilder,1);
    AAudioStreamBuilder_setPerformanceMode(streamBuilder,AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
    AAudioStreamBuilder_setDataCallback(streamBuilder,::playCallback, this);
    AAudioStreamBuilder_setErrorCallback(streamBuilder, ::playErrorCallback, this);
    AAudioStreamBuilder_setSampleRate(streamBuilder,SAMPLE_RATE);
    AAudioStreamBuilder_setDirection(streamBuilder,AAUDIO_DIRECTION_OUTPUT);


    //Open stream
    aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &this->playStream);
    if (result != AAUDIO_OK) {
        __android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error opening stream %s",
                            AAudio_convertResultToText(result));
        return false;
    }

    AAudioStream_setBufferSizeInFrames(this->playStream,AAudioStream_getFramesPerBurst(this->playStream)*2); //Set latency


    result = AAudioStream_requestStart(this->playStream);
    if (result != AAUDIO_OK) {
        __android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error starting stream %s",
                            AAudio_convertResultToText(result));
        return false;
    }

    AAudioStreamBuilder_delete(streamBuilder);

    return true;
}

void AudioEngine::restartPlaying() {
    static std::mutex restartingLock;

    if(restartingLock.try_lock()){
        this->stopPlaying();
        this->startPlaying();
        restartingLock.unlock();
    }
}

void AudioEngine::stopPlaying() {
    if(this->playStream != nullptr){
        AAudioStream_requestStop(this->playStream);
        AAudioStream_close(this->playStream);
    }
}

bool AudioEngine::startRecording() {
    AAudioStreamBuilder *streamBuilder;
    AAudio_createStreamBuilder(&streamBuilder);
    AAudioStreamBuilder_setFormat(streamBuilder,AAUDIO_FORMAT_PCM_I16);
    AAudioStreamBuilder_setChannelCount(streamBuilder,1);
    AAudioStreamBuilder_setPerformanceMode(streamBuilder,AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
    AAudioStreamBuilder_setDataCallback(streamBuilder,::recordCallback, this);
    AAudioStreamBuilder_setErrorCallback(streamBuilder, ::recordErrorCallback, this);
    AAudioStreamBuilder_setSampleRate(streamBuilder,SAMPLE_RATE);
    AAudioStreamBuilder_setDirection(streamBuilder,AAUDIO_DIRECTION_INPUT);

    //Open stream
    aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &this->recordStream);
    if (result != AAUDIO_OK) {
        __android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error opening stream %s",
                            AAudio_convertResultToText(result));
        return false;
    }

    int32_t sampleRate= AAudioStream_getSampleRate(this->recordStream);

    AAudioStream_setBufferSizeInFrames(this->recordStream, AAudioStream_getFramesPerBurst(this->recordStream)); //Set latency

    result = AAudioStream_requestStart(this->recordStream);
    if (result != AAUDIO_OK) {
        __android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error starting stream %s",
                            AAudio_convertResultToText(result));
        return false;
    }

    AAudioStreamBuilder_delete(streamBuilder);

    return true;
}

void AudioEngine::restartRecording() {
    static std::mutex restartingLock;

    if(restartingLock.try_lock()){
        this->stopRecording();
        this->startRecording();
        restartingLock.unlock();
    }
}

void AudioEngine::stopRecording() {
    if(this->recordStream != nullptr){
        AAudioStream_requestStop(this->recordStream);
        AAudioStream_close(this->recordStream);
    }
}
